﻿/*
 * Programming Graphical User Interfaces
 * Example program: Diagram
 * DiagramView.cs: Implements a graphical view of the diagram.
 * Jarkko Leponiemi 2004
 */

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using SharppiFlowEditor.models;
using SharppiFlowEditor.controllers;


namespace SharppiFlowEditor.views
{
    /*
    * A class representing a graphical view of the diagram model.
    */
    public class AppView : ScrollableControl
    {

        // reference to the data model
        private AppModel diagram;
        // reference to the current controller
        private DiagramController contr;
        // drawing objects
        private Pen nPen, cPen;
        private Font nFont;
        private Brush nBrush, nTextBrush, nShadowBrush;
        private float nMargin;
        // node rectangle cache
        private Hashtable rCache;

        public Point ViewScrollPosition
        {
            get { return AutoScrollPosition; }
        }

        private Object _selectedObject;
        public Object SelectedObject
        {
            set { _selectedObject = value; }
            get { return _selectedObject; }
        }

        private Size _viewSize;
        public Size AppViewSize
        {
            get { return _viewSize; }
            set { _viewSize = value; }
        }

        // Application main menu
        private MainMenu appMainMenu = new MainMenu();
        private MenuItem fileMenu = new MenuItem("File");
        private MenuItem newDocumentMenuItem = new MenuItem("&New flowchart...");
        private MenuItem saveDocumentMenuItem = new MenuItem("&Save");
        private MenuItem saveAsDocumentMenuItem = new MenuItem("Save as...");
        private MenuItem openDocumentMenuItem = new MenuItem("&Open...");
        private MenuItem printDocumentMenuItem = new MenuItem("&Print...");
        private MenuItem documentOverviewMenuItem = new MenuItem("Print over&view...");
        private MenuItem exitAppMenuItem = new MenuItem("&Exit");

        // Application toolbar
        private ToolBar appToolBar = new ToolBar();
        private ImageList appToolBarImageList = new ImageList();
        private ToolBarButton nodeToolButton = new ToolBarButton();
        private ToolBarButton connectorToolButton = new ToolBarButton();
        private ToolBarButton eraserToolButton = new ToolBarButton();





        /*
         * Create the view object for the specified diagram model.
         */
        public AppView(AppModel diagram)
        {
            this.diagram = diagram;
            contr = null;
            rCache = new Hashtable();
            // create observer-observable connection
            diagram.DiagramChange += new DiagramEventHandler(UpdateView);
            // create update event handler
            Paint += new PaintEventHandler(PaintView);

            // init drawing objects
            nPen = new Pen(Color.Black);
            cPen = new Pen(Color.Black);
            nFont = new Font(FontFamily.GenericSansSerif, 10);
            nBrush = new SolidBrush(Color.White);
            nTextBrush = new SolidBrush(Color.Black);
            nShadowBrush = new SolidBrush(Color.FromArgb(51, Color.Black));
            nMargin = 5;
            // other settings
            ResizeRedraw = true;
            // use double buffering
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.DoubleBuffer, true);

            Dock = DockStyle.Fill;
            
            this.AutoScroll = true;
            this.AutoScrollMinSize = new Size(900, 900);
            
        }

        /*
         * Get the model of this view.
         */
        public AppModel Model
        {
            get { return diagram; }
        }

        /*
         * Get and set the current controller.
         */
        public DiagramController Controller
        {
            get { return contr; }
            set
            {
                // for the old controller, remove references to the view
                if (contr != null)
                {
                    contr.View = null;
                    MouseDown -= new MouseEventHandler(contr.ViewMouseDown);
                    MouseMove -= new MouseEventHandler(contr.ViewMouseMove);
                    MouseUp -= new MouseEventHandler(contr.ViewMouseUp);
                }
                contr = value;
                // initialize the controller to listen to mouse events
                if (contr != null)
                {
                    contr.View = this;
                    MouseDown += new MouseEventHandler(contr.ViewMouseDown);
                    MouseMove += new MouseEventHandler(contr.ViewMouseMove);
                    MouseUp += new MouseEventHandler(contr.ViewMouseUp);
                }

            }
        }

        /*
         * Called when the model changes.
         */
        public void UpdateView(object sender, FlowChartEventArgs e)
        {
            // remove the cached bounds rectangle of the (possible)
            // changed node
            if (e.NodeChanged != null)
            {
                rCache.Remove(e.NodeChanged);
            }
            // it this is the last change in a series of changes
            // update the view
            if (e.LastChange)
            {
                Invalidate();
                Update();
            }
        }

        /*
         * Paint the contents of the view.
         */
        public void PaintView(object sender, PaintEventArgs pea)
        {
            Graphics g = pea.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;

            //handle the possibility that the viewport is scrolled,
            //adjust my origin coordintates to compensate
            Point pt = AutoScrollPosition;
            g.TranslateTransform(pt.X, pt.Y);

            // draw the connectors
            IEnumerator e = diagram.Connectors.GetEnumerator();
            while (e.MoveNext())
            {
                Connector c = (Connector)e.Current;
                DrawConnector(g, c);
            }
            // draw the nodes on top of the connectors
            e = diagram.Nodes.GetEnumerator();
            while (e.MoveNext())
            {
                if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.ActionNode" ||
                    e.Current.GetType().ToString() == "SharppiFlowEditor.models.EndNode" ||
                    e.Current.GetType().ToString() == "SharppiFlowEditor.models.ConditionalNode") 
                {
                    AbstractNode an = (AbstractNode)e.Current;
                    an.draw(g);
                }
                
            }
            // let controller draw any temporary images
            if (contr != null)
            {
                contr.DrawTempImage(g);
            }
        }

        /*
         * Get and set the pen used for drawing node frame.
         * TO DO: Invalidate, and update contents!
         */
        public Pen NodePen
        {
            get { return nPen; }
            set { nPen = value; }
        }

        /*
         * Get and set the pen used for drawing connector lines.
         * TO DO: Invalidate, and update contents!
         */
        public Pen ConnectorPen
        {
            get { return cPen; }
            set { cPen = value; }
        }

        /*
         * Get and set the font used for drawing the node title.
         * TO DO: Invalidate, empty cache and update contents!
         */
        public Font NodeFont
        {
            get { return nFont; }
            set { nFont = value; }
        }

        /*
         * Get and set the brush used for drawing the node background.
         * TO DO: Invalidate, and update contents!
         */
        public Brush NodeBrush
        {
            get { return nBrush; }
            set { nBrush = value; }
        }

        /*
         * Get and set the brush used for drawing the node title.
         * TO DO: Invalidate, and update contents!
         */
        public Brush NodeTextBrush
        {
            get { return nTextBrush; }
            set { nTextBrush = value; }
        }

        /*
         * Get and set the internal margin of nodes.
         * TO DO: Invalidate, empty cache and update contents!
         */
        public float NodeMargin
        {
            get { return nMargin; }
            set { nMargin = value; }
        }



        /*
         * Draw one connector.
         */
        protected void DrawConnector(Graphics g, Connector c)
        {
            Pen p = new Pen(c.Color);

            Point centerPoint1 = new Point(c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2,
                                            c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2);

            Point centerPoint2 = new Point(c.Node2.getBoundingRect().X + c.Node2.getBoundingRect().Width / 2,
                                            c.Node2.getBoundingRect().Y + c.Node2.getBoundingRect().Height / 2);

            g.DrawLine(p, centerPoint1, centerPoint2);



            g.DrawString(c.Text, new Font("verdana", 10F), new SolidBrush(Color.Black), new PointF(
                (Math.Max(centerPoint1.X, centerPoint2.X) - Math.Min(centerPoint1.X, centerPoint2.X)) / 2 + Math.Min(centerPoint1.X, centerPoint2.X),
                ((Math.Max(centerPoint1.Y, centerPoint2.Y) - Math.Min(centerPoint1.Y, centerPoint2.Y)) / 2) + Math.Min(centerPoint1.Y, centerPoint2.Y)
                ));
        }

        
        

        /*
         * Get the last node including the given coordinate point.
         */
        internal AbstractNode GetNode(float x, float y)
        {
            IEnumerator e = diagram.Nodes.GetEnumerator();
            //Node last = null;
            while (e.MoveNext())
            {
                AbstractNode an;
                if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.ActionNode")
                {   an = (ActionNode)e.Current;
                    if (an.getBoundingRect().Contains((int)x, (int)y))
                        return an;
                }
                else if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.EndNode")
	            {
		            an = (EndNode)e.Current;
                    if (an.getBoundingRect().Contains((int)x, (int)y))
                        return an;
	            }
                else
                {
                    an = (ConditionalNode)e.Current;
                    if (an.getBoundingRect().Contains((int)x, (int)y))
                        return an;
                }                

            }
            return null;
        }

        public Connector getConnectorAtPoint(PointF location)
        {
            
            Connector con = null;

            
            foreach (Connector c in diagram.Connectors)
            {
                c.Color = Color.Black;
            }

            foreach (Connector c in diagram.Connectors)
            {
                Point centerPoint1 = new Point(c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2,
                                            c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2);

                PointF p1 = new PointF(c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2,
                                            c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2);
                PointF p2 = new PointF(c.Node2.getBoundingRect().X + c.Node2.getBoundingRect().Width / 2,
                                            c.Node2.getBoundingRect().Y + c.Node2.getBoundingRect().Height / 2);

                PointF leftNode = p2;
                PointF upperNode = p2;

                if (p1.X < p2.X)
                    leftNode = p1;
                if (p1.Y > p2.Y)
                    upperNode = p1;

                Console.WriteLine("click: " + location.X + "," + location.Y);

                float slope = (p2.Y - p1.Y) / (p2.X - p1.X);

                //pakotetaan slope numeeriseen muotoon:
                if (float.IsNaN(slope))
                    slope = 0F;

                Console.WriteLine("slope: " + slope);

                float correspondingY = slope * (location.X - leftNode.X) + leftNode.Y;
                float correspondingX = (location.Y - leftNode.Y) / slope + leftNode.X;

                float distX = correspondingX - location.X;
                float distY = correspondingY - location.Y;

                if (distX <= 4 &&
                    distX >= -4 ||
                    distY <= 4 &&
                    distY >= -4)
                {
                    Console.WriteLine("connector!");
                    c.Color = Color.Red;
                    con = c;
                    return con;
                }
                else
                {
                    con = null;
                }
            }
            Invalidate();
            Update();

            return con;
        }


    }
}